package edu.uci.ics.multicast.overlay;

import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Random;

import org.dom4j.DocumentException;

public class ShellScriptGenerator {

	private enum AlgorithmType {
		KIM, RANDOM;
	}
	private AlgorithmType algorithmType = AlgorithmType.KIM;
	
	private static final int T_NEIGHBOR = 3;
	private static final double T_DISTANCE = 5000;
	private static final int RANDOM_NEIGHBOR = 30;
	
	private static final double GEO_WIDTH = 2000;
	private static final double GEO_HEIGHT = 2000;
	private static final double GEO_X = 5000;
	private static final double GEO_Y = 5000;
	
	private static final String SCRIPT_FILE = "meshNetwork.sh";
	
	private Topology topology;
	private HashMap<OverlayNode, List<OverlayNode>> meshNetwork = new HashMap<OverlayNode, List<OverlayNode>>();

	public ShellScriptGenerator() throws DocumentException {
		topology = new Topology();
		ImportTopology importTopology = new ImportTopology(topology);
		importTopology.load();
	}

	public Topology getTopology() {
		return topology;
	}
	
	public HashMap<OverlayNode, List<OverlayNode>> getMeshNetwork() {
		return meshNetwork;
	}
	
	public void writeScriptFile(String scriptFile, HashSet<OverlayNode> listOfAffectedNodes) throws IOException {
		FileWriter outFile = new FileWriter(scriptFile);
		PrintWriter out = new PrintWriter(outFile);

		HashSet<OverlayNode> listScriptFathers = new LinkedHashSet<OverlayNode>();
		HashSet<OverlayNode> listScriptChildren = new LinkedHashSet<OverlayNode>();
		
		for (Map.Entry<OverlayNode, List<OverlayNode>> entry : meshNetwork.entrySet()) {
			OverlayNode fatherNode = entry.getKey();
			if (!listOfAffectedNodes.contains(fatherNode)) {
				List<OverlayNode> listChildren = entry.getValue();
				String strChildren = "";
				for (OverlayNode child : listChildren) {
					if (!listOfAffectedNodes.contains(child)) {
						strChildren += child.getIp() + ",";
						listScriptChildren.add(child);
					}
				}
				if (!"".equals(strChildren)) {
					strChildren = strChildren.substring(0, strChildren.length()-1);
					out.println("LD_PRELOAD=/usr/lib/libipaddr.so SRCIP=" + 
								fatherNode.getIp() + 
								" java -jar peer.jar -b " + 
								fatherNode.getIp() +
								" -h " +
								strChildren +
								" -t 60000" +
								" -s" +
								" >> results.txt" +
								" &");
					listScriptFathers.add(fatherNode);
				}
			}
		}
		for (OverlayNode child : listScriptChildren) {
			if (!listScriptFathers.contains(child)) {
				out.println("LD_PRELOAD=/usr/lib/libipaddr.so SRCIP=" + 
						child.getIp() + 
						" java -jar peer.jar -b " + 
						child.getIp() +
						" -t 60000" +
						" -s" +
						" >> results.txt" +
						" &");
			}
		}
		out.close();
	}
	
	public HashSet<OverlayNode> getListOfReachableNodesAfterGeographicalFailure(OverlayNode rootNode, GeographicArea geoArea) {
		HashSet<OverlayNode> listOfReachableNodes = new LinkedHashSet<OverlayNode>();
		List<Path> listPaths = rootNode.getListPath();
		for (Path path : listPaths) {
			boolean isReachable = true;
			List<Edge> listEdges = path.getListEdges();
			for (Edge edge : listEdges) {
				MulticastNode multicastNode = edge.getSource();
				if (multicastNode instanceof PhysicalNode) {
					PhysicalNode physicalNode = (PhysicalNode) multicastNode;
					if (geoArea.contains(physicalNode)) {
						isReachable = false;
						break;
					}
				}
			}
			if (isReachable) {
				listOfReachableNodes.add(path.getDestinationNode());
			}
		}
		return listOfReachableNodes;
	}

	public HashSet<OverlayNode> getListOfAffectedNodesByGeographicalFailure(GeographicArea geoArea) {
		HashSet<OverlayNode> listOfAffectedNodes = new LinkedHashSet<OverlayNode>();
		for (Map.Entry<OverlayNode, List<OverlayNode>> entry : meshNetwork.entrySet()) {
			OverlayNode fatherNode = entry.getKey();
			List<OverlayNode> lisChildren = entry.getValue();
			for (OverlayNode childNode : lisChildren) {
				List<Path> listPaths = fatherNode.getListPath();
				int numberOfPaths = 0;
				int numberOfBrokenPaths = 0;
				for (Path path : listPaths) {
					if (path.getDestinationNode().equals(childNode)) {
						numberOfPaths++;
						List<Edge> listEdges = path.getListEdges();
						for (Edge edge : listEdges) {
							MulticastNode multicastNode = edge.getSource();
							if (multicastNode instanceof PhysicalNode) {
								PhysicalNode physicalNode = (PhysicalNode) multicastNode;
								if (geoArea.contains(physicalNode)) {
									numberOfBrokenPaths++;
									break;
								}
							}
						}
					}
				} // end-for path
				if (numberOfPaths!=0 && numberOfPaths==numberOfBrokenPaths) {
					listOfAffectedNodes.add(childNode);
				}
			}
		}
		return listOfAffectedNodes;
	}

	private static List<OverlayNode> generateListOfRandomNodes(OverlayNode overlayNode, List<OverlayNode> listTotalOverlayNodes, int randomNeighbor) {
		List<OverlayNode> listOfRandomCandidates = new LinkedList<OverlayNode>();
		Random generator = new Random();
		for (int i = 0; i < randomNeighbor ; i++) {
			int numberOfNodes = listTotalOverlayNodes.size();
			int index = generator.nextInt(numberOfNodes);
			index = (index==numberOfNodes) ? index-1 : index;
			OverlayNode candidateNode = listTotalOverlayNodes.get(index);
			if ( !candidateNode.equals(overlayNode) && !listOfRandomCandidates.contains(candidateNode) ) {
				listOfRandomCandidates.add(candidateNode);	
			}
		}
		return listOfRandomCandidates;
	}
	
	public void execute(AlgorithmType algorithmType, int tNeighbor, double tDistance) throws DocumentException {
		KimAlgorithm kimAlgorithm = new KimAlgorithm(topology, tNeighbor, tDistance);
		List<OverlayNode> listTotalOverlayNodes = topology.getListOverlayNodes();

		if (algorithmType==AlgorithmType.KIM) {
			for (OverlayNode overlayNode : listTotalOverlayNodes) {
				List<OverlayNode> listOfRandomCandidates = generateListOfRandomNodes(overlayNode,listTotalOverlayNodes,RANDOM_NEIGHBOR); 
				List<OverlayNode> listOfNeighbors = kimAlgorithm.selectNeighbors(overlayNode,listOfRandomCandidates);
				meshNetwork.put(overlayNode, listOfNeighbors);
			}
		} else if (algorithmType==AlgorithmType.RANDOM) {
			for (OverlayNode overlayNode : listTotalOverlayNodes) {
				List<OverlayNode> listOfNeighbors = generateListOfRandomNodes(overlayNode,listTotalOverlayNodes,tNeighbor);
				meshNetwork.put(overlayNode, listOfNeighbors);
			}
		}
	}
	
	/**
	 * @param args
	 * @throws DocumentException 
	 * @throws IOException 
	 */
	public static void main(String[] args) throws DocumentException, IOException {
		ShellScriptGenerator shellScriptGenerator = new ShellScriptGenerator();
		shellScriptGenerator.execute(shellScriptGenerator.algorithmType, T_NEIGHBOR, T_DISTANCE);

		GeographicArea geoArea = new GeographicArea(GEO_X, GEO_Y, GEO_WIDTH, GEO_HEIGHT);
		HashSet<OverlayNode> listOfAffectedNodes = shellScriptGenerator.getListOfAffectedNodesByGeographicalFailure(geoArea);

		int numberOfAffectedNodes = listOfAffectedNodes.size();
		int numberNodesReceivingMessage = ImportTopology.TOTAL_NUMBER_OF_OVERLAY_NODES - numberOfAffectedNodes;
		OverlayNode rootNode = shellScriptGenerator.getTopology().getListOverlayNodes().get(0);
		HashSet<OverlayNode> listReachableNodesFromRoot = shellScriptGenerator.getListOfReachableNodesAfterGeographicalFailure(rootNode, geoArea);
		int numberNodesReachableFromRoot = listReachableNodesFromRoot.size();

		if (numberNodesReachableFromRoot>0) {
			double realiability = (double)numberNodesReceivingMessage / (double)numberNodesReachableFromRoot;
			System.out.println("numberOfAffectedNodes: " + numberOfAffectedNodes);
			System.out.println("numberNodesReceivingMessage: " + numberNodesReceivingMessage);
			System.out.println("realiability: " + realiability*100 + "%");
		} else {
			System.err.println("numberNodesReachableFromRoot= "+ 0);
		}
		
		shellScriptGenerator.writeScriptFile(SCRIPT_FILE, listOfAffectedNodes);
	}
} // end-of class ShellScriptGenerator

class GeographicArea {
	double x;
	double y;
	double width;
	double height;

	public GeographicArea(double x, double y, double width, double height) {
		this.x = x;
		this.y = y;
		this.width = width;
		this.height = height;
	}

	/**
	 * isPhysicalNodeAffected
	 */
	public boolean contains(PhysicalNode physicalNode) {
		double x = physicalNode.getX();
		double y = physicalNode.getY();
		if(x < this.x)
			return false;
		if(x > this.x + width)
			return false;
		if(y < this.y)
			return false;
		if(y > this.y + height)
			return false;
		return true;
	}
}
